using System;
using System.Collections.Generic;
using System.Text;
using DslFactory.Utilities.DteCodeModel;
using EnvDTE;
using EnvDTE80;
using Microsoft.VisualStudio.Modeling;
using System.Net.Security;

namespace Dslfactory.wcfLanguage
{
 internal static class DataContractIncrementalCodeGeneration
 {
  /// <summary>
  /// Subscribe to Interface code generation events
  /// </summary>
  /// <param name="Store"></param>
  /// <param name="eventManagerDirectory"></param>
  public static void SubsribeToStoreEvents(Store Store)
  {
   EventManagerDirectory eventManagerDirectory = Store.EventManagerDirectory;

   // When a property of the Interface changes, the [ContractAttribute] must be updated in code
   eventManagerDirectory.ElementPropertyChanged.Add(Store.DomainDataDirectory.GetDomainClass(typeof(DataContract)),
                                                          new EventHandler<ElementPropertyChangedEventArgs>(EnsureContractAttribute));

   // When a property of the DataMember / OperationContract changes, the [OperationContract] attribute must be updated in code
   eventManagerDirectory.ElementPropertyChanged.Add(Store.DomainDataDirectory.GetDomainClass(typeof(DataMember)),
                                                               new EventHandler<ElementPropertyChangedEventArgs>(EnsureDataMemberContractAttribute));

   // When a method is removed from an Interface, the corresponding code must be deleted. When one is added, code must be added
   eventManagerDirectory.ElementDeleted.Add(Store.DomainDataDirectory.GetDomainClass(typeof(DataContractHasDataMembers)),
                                                         new EventHandler<ElementDeletedEventArgs>(EnsuresDataMemberRemoved));
   eventManagerDirectory.ElementAdded.Add(Store.DomainDataDirectory.GetDomainClass(typeof(DataContractHasDataMembers)),
                                                         new EventHandler<ElementAddedEventArgs>(EnsuresDataMemberAdded));
  }



  /// <summary>
  /// Ensures the operation properties of which changed is updated
  /// </summary>
  private static void EnsureDataMemberContractAttribute(object sender, ElementPropertyChangedEventArgs arg)
  {
   DataMember member = arg.ModelElement as DataMember;

/*
   // Name
   if (arg.DomainProperty.Id == DataMember.NameDomainPropertyId)
   {
    // Refactor rename
    string oldName = arg.OldValue as string;
    string newName = arg.NewValue as string;
    CodeElement2 element = FindMember(member, oldName) as CodeElement2;
    if (element != null)
     element.RenameSymbol(arg.NewValue as string);
   }

   // Retype
   else if (arg.DomainProperty.Id == DataMember.MemberFullTypeNameDomainPropertyId)
   {
    CodeElement element = FindMember(member, member.Name);
    if (element!=null)
     IncrementalGenerator.EnsureMemberType(element, member.MemberFullTypeName);
   }

   else
*/ 
    SynchronizeCodeWithModel(member);
  }

  /// <summary>
  /// Ensure an attribute for interfaces
  /// </summary>
  /// <param name="sender"></param>
  /// <param name="arg"></param>
  private static void EnsureContractAttribute(object sender, ElementPropertyChangedEventArgs arg)
  {
   DataContract dataContract = arg.ModelElement as DataContract;

   // Name
   if (arg.DomainProperty.Id == DataContract.NameDomainPropertyId)
   {
    // Get the class
    string oldFullName = (string.IsNullOrEmpty(dataContract.Namespace) ? arg.OldValue as string : dataContract.Namespace + '.' + arg.OldValue as string);
    KnownCodeTypes knownCodeTypes = KnownCodeTypes.FromStore(dataContract.Store);
    CodeClass2 codeClass = knownCodeTypes.GetFullNamedType(oldFullName) as CodeClass2;
    CodeElement2 element = codeClass as CodeElement2;
    if (element != null)
    {
     element.RenameSymbol(arg.NewValue as string);
     knownCodeTypes.TakeIntoAccount(element as CodeType, true);
    }
   }
   

   else
    SynchronizeCodeWithModel(dataContract);
  }



  /// <summary>
  /// Synchronizes code from model for an Interface or a ServiceContract
  /// </summary>
  /// <param name="iface">Interface for which we want to update code</param>
  private static void SynchronizeCodeWithModel(DataContract dataContract)
  {
   KnownCodeTypes knownCodeTypes = KnownCodeTypes.FromStore(dataContract.Store);
   if (knownCodeTypes == null)
    return;

   // Get the class
   CodeClass2 codeClass2 = knownCodeTypes.GetFullNamedType(dataContract.FullName) as CodeClass2;
   if (codeClass2 != null)
   {

    // Get the attribute (if necessary)
    CodeAttribute attribute = IncrementalGenerator.EnsureAttributeIff(codeClass2, "System.Runtime.Serialization.DataContractAttribute", dataContract.IsDataContract);

    // Properties are mapped to AttributeArguments
    if (attribute != null)
    {
     IncrementalGenerator.EnsureAttributeArgumentIff(attribute, "Name", '"' + dataContract.DataContractName + '"', !string.IsNullOrEmpty(dataContract.DataContractName));
     // Modifying the properties invalidates the attribute, so re-ask for it.
     attribute = IncrementalGenerator.FindFirstAttribute(codeClass2, "System.Runtime.Serialization.DataContractAttribute");
     IncrementalGenerator.EnsureAttributeArgumentIff(attribute, "Namespace", '"' + dataContract.DataContractNamespace + '"', !string.IsNullOrEmpty(dataContract.DataContractNamespace));
    }

    // Synchronize members
    foreach (DataMember member in dataContract.DataMembers)
     SynchronizeCodeWithModel(member);
   }
  }


  /// <summary>
  /// Updates the method code from the model
  /// </summary>
  /// <param name="method"></param>
  private static void SynchronizeCodeWithModel(DataMember dataMember)
  {
   if (dataMember.DataContract == null)
    return;

   KnownCodeTypes knownCodeTypes = KnownCodeTypes.FromStore(dataMember.Store);
   if (knownCodeTypes == null)
    return;

   // Get the class
   CodeClass2 codeClass2 = knownCodeTypes.GetFullNamedType(dataMember.DataContract.FullName) as CodeClass2;
   if (codeClass2 == null)
    return;

   CodeElement codeDataMember = IncrementalGenerator.FindFirstMember(codeClass2, dataMember.Name);



   // Get the attribute (if necessary)
   CodeAttribute attribute = null;
   if (codeDataMember is CodeVariable)
    attribute = IncrementalGenerator.EnsureAttributeIff(codeDataMember as CodeVariable, "System.Runtime.Serialization.DataMemberAttribute", dataMember.IsDataMember);
   else if (codeDataMember is CodeProperty)
    attribute = IncrementalGenerator.EnsureAttributeIff(codeDataMember as CodeProperty, "System.Runtime.Serialization.DataMemberAttribute", dataMember.IsDataMember);

   // Properties
   if ( (dataMember != null) && (attribute!=null) )
   {
    IncrementalGenerator.EnsureAttributeArgumentIff(attribute, "Name", '"'+dataMember.DataMemberName+'"', !string.IsNullOrEmpty(dataMember.DataMemberName));
    IncrementalGenerator.EnsureAttributeArgumentIff(attribute, "IsRequired", dataMember.IsRequired.ToString().ToLower(), !dataMember.IsRequired);
    IncrementalGenerator.EnsureAttributeArgumentIff(attribute, "Order", dataMember.Order.ToString().ToLower(), dataMember.Order!= -1);
    IncrementalGenerator.EnsureAttributeArgumentIff(attribute, "EmitDefaultValue", dataMember.EmitDefaultValue.ToString().ToLower(), !dataMember.EmitDefaultValue);
   }
  }

  /// <summary>
  /// Ensures the operation properties of which changed is updated
  /// </summary>
  private static void EnsuresDataMemberRemoved(object sender, ElementEventArgs arg)
  {
   DataContractHasDataMembers dataContractHasDataMember = arg.ModelElement as DataContractHasDataMembers;
   DataMember dataMember = dataContractHasDataMember.DataMember;
   DataContract dataContract = dataContractHasDataMember.DataContract;

   // Important : Do not remove methods when the interface is removed from diagram
   if ((dataContract.IsDeleting) || (dataContract.IsDeleted))
    return;

   CodeClass2 codeClass = KnownCodeTypes.FromStore(dataMember.Store).GetFullNamedType(dataContract.FullName) as CodeClass2;
   if (codeClass == null)
    return;
   CodeElement codeDataMember = IncrementalGenerator.FindFirstMember(codeClass, dataMember.Name);
   if (codeDataMember == null)
    return;
   IncrementalGenerator.RemoveOrCommentDataMember(codeDataMember);
  }


  /// <summary>
  /// Ensures the operation properties of which changed is updated
  /// </summary>
  private static void EnsuresDataMemberAdded(object sender, ElementEventArgs arg)
  {
   DataContractHasDataMembers dataContractHasDataMember = arg.ModelElement as DataContractHasDataMembers;
   DataMember dataMember = dataContractHasDataMember.DataMember;
   DataContract dataContract = dataContractHasDataMember.DataContract;

   CodeClass2 codeInterface = KnownCodeTypes.FromStore(dataContract.Store).GetFullNamedType(dataContract.FullName) as CodeClass2;
   if (codeInterface == null)
    return;

   if (dataMember.Kind == DataMemberKind.Field)
   {
    CodeVariable v = IncrementalGenerator.EnsureField(codeInterface, dataMember.Name, dataMember.MemberFullTypeName);
    vsCMAccess access = DataClassIncrementalCodeGeneration.GetAccessFromVisibility(dataMember.Visibility);
    if (v.Access != access)
     v.Access = access;
   }
   else if (dataMember.Kind == DataMemberKind.Property)
   {
    CodeProperty p = IncrementalGenerator.EnsureProperty(codeInterface, dataMember.Name, string.IsNullOrEmpty(dataMember.MemberFullTypeName) ? "int" : dataMember.MemberFullTypeName);
    vsCMAccess access = DataClassIncrementalCodeGeneration.GetAccessFromVisibility(dataMember.Visibility);
    if (p.Access != access)
     p.Access = access;
   }

   SynchronizeCodeWithModel(dataMember);
  }


  internal static void Generate(TypeOfInterest type, CodeNamespace ns)
  {
   DataContract dataContract = type as DataContract;
   CodeClass2 codeClass = IncrementalGenerator.EnsureClass(ns, type.Name) as CodeClass2;
   KnownCodeTypes.FromStore(type.Store).TakeIntoAccount(codeClass as CodeType, false);

   DataClassIncrementalCodeGeneration.GenerateMembers(codeClass, dataContract);

   SynchronizeCodeWithModel(dataContract);
  }
 }
}
